package com.beta.nfc_beta;


/*
 * Copyright 2013 Gerhard Klostermeier
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */




import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import android.content.Context;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.NfcA;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.util.SparseArray;

public class Common {
	public enum Operations {
        Read, Write, Increment, DecTransRest, ReadKeyA, ReadKeyB, ReadAC,
        WriteKeyA, WriteKeyB, WriteAC
    }
	
	 public static final String HOME_DIR = "/MifareClassicTool";
	    /**
	     * The directory name  of the key files directory.
	     * (sub directory of {@link #HOME_DIR}.)
	     */
	    public static final String KEYS_DIR = "/key-files";
	    /**
	     * The directory name  of the dump files directory.
	     * (sub directory of {@link #HOME_DIR}.)
	     */
	    public static final String DUMPS_DIR = "/dump-files";
	    /**
	     * The directory name of the folder where temporary files are
	     * stored. The directory will be cleaned during the creation of
	     * the main activity ({@link Activities.MainActivity}).
	     * (sub directory of {@link #HOME_DIR}.)
	     */
	    public static final String TMP_DIR = "/tmp";
	    /**
	     * This file contains some standard Mifare keys.
	     * <ul>
	     * <li>0xFFFFFFFFFFFF - Unformatted, factory fresh tags.</li>
	     * <li>0xA0A1A2A3A4A5 - First sector of the tag (Mifare MAD).</li>
	     * <li>0xD3F7D3F7D3F7 - All other sectors.</li>
	     * <li>Others from {@link #SOME_CLASSICAL_KNOWN_KEYS}.</li>
	     * </ul>
	     */
	    public static final String STD_KEYS = "std.keys";

	    /**
	     * Some classical Mifare keys retrieved by a quick google search
	     * ("mifare standard keys").
	     */
	    public static final String[] SOME_CLASSICAL_KNOWN_KEYS =
	        {   "000000000000",
	            "A0B0C0D0E0F0",
	            "A1B1C1D1E1F1",
	            "B0B1B2B3B4B5",
	            "4D3A99C351DD",
	            "1A982C7E459A",
	            "AABBCCDDEEFF"  };
	    private static final String LOG_TAG = Common.class.getSimpleName();

	    /**
	     * The last detected tag.
	     * Set by {@link #treatAsNewTag(Intent, Context)}
	     */
	    private static Tag mTag = null;
	    /**
	     * The last detected UID.
	     * Set by {@link #treatAsNewTag(Intent, Context)}
	     */
	    private static byte[] mUID = null;
	    /**
	     * Just a global storage to save key maps generated by
	     * {@link Activities.CreateKeyMapActivity}
	     * @see Activities.CreateKeyMapActivity
	     * @see MCReader#getKeyMap()
	     */
	    private static SparseArray<byte[][]> mKeyMap = null;

	    private static NfcAdapter mNfcAdapter;

	    /**
	 public static byte[][] acToACMatrix(byte ac[]) {
	        // ACs correct?
	        // C1 (Byte 7, 4-7) == ~C1 (Byte 6, 0-3) and
	        // C2 (Byte 8, 0-3) == ~C2 (Byte 6, 4-7) and
	        // C3 (Byte 8, 4-7) == ~C3 (Byte 7, 0-3)
	        byte[][] acMatrix = new byte[3][4];
	        if ((byte)((ac[1]>>>4)&0x0F)  == (byte)((ac[0]^0xFF)&0x0F) &&
	            (byte)(ac[2]&0x0F) == (byte)(((ac[0]^0xFF)>>>4)&0x0F) &&
	            (byte)((ac[2]>>>4)&0x0F)  == (byte)((ac[1]^0xFF)&0x0F)) {
	            // C1, Block 0-3
	            for (int i = 0; i < 4; i++) {
	                acMatrix[0][i] = (byte)((ac[1]>>>4+i)&0x01);
	            }
	            // C2, Block 0-3
	            for (int i = 0; i < 4; i++) {
	                acMatrix[1][i] = (byte)((ac[2]>>>i)&0x01);
	            }
	            // C3, Block 0-3
	            for (int i = 0; i < 4; i++) {
	                acMatrix[2][i] = (byte)((ac[2]>>>4+i)&0x01);
	            }
	            return acMatrix;
	        }
	        return null;
	    }



	    /**
	     * Check if the given block (hex string) is a value block.
	     * NXP has PDFs describing what value blocks are. Google something
	     * like "nxp mifare classic value block" if you want to have a
	     * closer look.
	     * @param hexString Block data as hex string.
	     * @return True if it is a value block. False otherwise.
	     */
	    public static boolean isValueBlock(String hexString) {
	        byte[] b = Common.hexStringToByteArray(hexString);
	        if (b.length == 16) {
	            // Google some NXP info PDFs about Mifare Classic to see how
	            // Value Blocks are formated.
	            // For better reading (~ = invert operator):
	            // if (b0=b8 and b0=~b4) and (b1=b9 and b9=~b5) ...
	            // ... and (b12=b14 and b13=b15 and b12=~b13) then
	            if (    (b[0] == b[8] && (byte)(b[0]^0xFF) == b[4]) &&
	                    (b[1] == b[9] && (byte)(b[1]^0xFF) == b[5]) &&
	                    (b[2] == b[10] && (byte)(b[2]^0xFF) == b[6]) &&
	                    (b[3] == b[11] && (byte)(b[3]^0xFF) == b[7]) &&
	                    (b[12] == b[14] && b[13] == b[15] &&
	                    (byte)(b[12]^0xFF) == b[13])) {
	                return true;
	            }
	        }
	        return false;
	    }

	    /**
	     * Reverse a byte Array (e.g. Little Endian -> Big Endian).
	     * Hmpf! Java has no Array.reverse(). And I don't want to use
	     * Commons.Lang (ArrayUtils) form Apache....
	     * @param array The array to reverse (in-place).
	     */
	    public static void reverseByteArrasInPlace(byte[] array) {
	        for(int i = 0; i < array.length / 2; i++) {
	            byte temp = array[i];
	            array[i] = array[array.length - i - 1];
	            array[array.length - i - 1] = temp;
	        }
	    }

	    /**
	     * Convert an array of bytes into a string of hex values.
	     * @param bytes Bytes to convert.
	     * @return The bytes in hex string format.
	     */
	    public static String byte2HexString(byte[] bytes) {
	        String ret = "";
	        if (bytes != null) {
	            for (Byte b : bytes) {
	                ret += String.format("%02X", b.intValue() & 0xFF);
	            }
	        }
	        return ret;
	    }

	    /**
	     * Convert a string of hex data into a byte array.
	     * Original author is: Dave L. (http://stackoverflow.com/a/140861).
	     * @param s The hex string to convert
	     * @return An array of bytes with the values of the string.
	     */
	    public static byte[] hexStringToByteArray(String s) {
	        int len = s.length();
	        byte[] data = new byte[len / 2];
	        try {
	            for (int i = 0; i < len; i += 2) {
	                data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
	                                     + Character.digit(s.charAt(i+1), 16));
	            }
	        } catch (Exception e) {
	            Log.d("Log", "Argument(s) for hexStringToByteArray(String s)"
	                    + "was not a hex string");
	        }
	        return data;
	    }

	    /**
	     * Create a colored string.
	     * @param data The text to be colored.
	     * @param color The color for the text.
	     * @return A colored string.
	     */
	    public static SpannableString colorString(String data, int color) {
	        SpannableString ret = new SpannableString(data);
	        ret.setSpan(new ForegroundColorSpan(color),
	                0, data.length(), 0);
	        return ret;
	    }

	    /**
	     * Get the current active (last detected) Tag.
	     * @return The current active Tag.
	     * @see #mTag
	     */
	    public static Tag getTag() {
	        return mTag;
	    }

	    /**
	     * Set the new active Tag.
	     * @param tag The new Tag.
	     */
	    public static void setTag(Tag tag) {
	        mTag = tag;
	    }

	    /**
	     * Get the App wide used NFC adapter.
	     * @return NFC adapter.
	     */
	    public static NfcAdapter getNfcAdapter() {
	        return mNfcAdapter;
	    }

	    /**
	     * Set the App wide used NFC adapter.
	     * @param nfcAdapter The NFC adapter that should be used.
	     */
	    public static void setNfcAdapter(NfcAdapter nfcAdapter) {
	        mNfcAdapter = nfcAdapter;
	    }

	    /**
	     * Get the key map generated by {@link Activities.CreateKeyMapActivity}.
	     * @return A key map (see {@link MCReader#getKeyMap()}).
	     */
	    public static SparseArray<byte[][]> getKeyMap() {
	        return mKeyMap;
	    }

	    /**
	     * Set the key map.
	     * @param value A key map (see {@link MCReader#getKeyMap()}).
	     */
	    public static void setKeyMap(SparseArray<byte[][]> value) {
	        mKeyMap = value;
	    }

	    /**
	     * Get the UID of the current tag.
	     * @return The UID of the current tag.
	     * @see #mUID
	     */
	    public static byte[] getUID() {
	        return mUID;
	    }
	    
	    public static boolean isKeyBReadable(byte c1, byte c2, byte c3) {
	        if (c1 == 0
	                && (c2 == 0 && c3 == 0)
	                || (c2 == 1 && c3 == 0)
	                || (c2 == 0 && c3 == 1)) {
	            return true;
	        }
	        return false;
	    }

	    public static byte[][] acToACMatrix(byte ac[]) {
	        // ACs correct?
	        // C1 (Byte 7, 4-7) == ~C1 (Byte 6, 0-3) and
	        // C2 (Byte 8, 0-3) == ~C2 (Byte 6, 4-7) and
	        // C3 (Byte 8, 4-7) == ~C3 (Byte 7, 0-3)
	        byte[][] acMatrix = new byte[3][4];
	        if ((byte)((ac[1]>>>4)&0x0F)  == (byte)((ac[0]^0xFF)&0x0F) &&
	            (byte)(ac[2]&0x0F) == (byte)(((ac[0]^0xFF)>>>4)&0x0F) &&
	            (byte)((ac[2]>>>4)&0x0F)  == (byte)((ac[1]^0xFF)&0x0F)) {
	            // C1, Block 0-3
	            for (int i = 0; i < 4; i++) {
	                acMatrix[0][i] = (byte)((ac[1]>>>4+i)&0x01);
	            }
	            // C2, Block 0-3
	            for (int i = 0; i < 4; i++) {
	                acMatrix[1][i] = (byte)((ac[2]>>>i)&0x01);
	            }
	            // C3, Block 0-3
	            for (int i = 0; i < 4; i++) {
	                acMatrix[2][i] = (byte)((ac[2]>>>4+i)&0x01);
	            }
	            return acMatrix;
	        }
	        return null;
	    }
	    public static String[] readFileLineByLine(File file, boolean readComments) {
	        BufferedReader br = null;
	        String[] ret = null;
	        if (file != null && file.exists()) {
	            try {
	                br = new BufferedReader(new FileReader(file));

	                String line;
	                ArrayList<String> linesArray = new ArrayList<String>();
	                while ((line = br.readLine()) != null)   {
	                    // Ignore empty lines.
	                    // Ignore comments if readComments == false.
	                    if ( !line.equals("")
	                            && (readComments || !line.startsWith("#"))) {
	                        linesArray.add(line);
	                    }
	                }
	                if (linesArray.size() > 0) {
	                    ret = linesArray.toArray(new String[linesArray.size()]);
	                } else {
	                    ret = new String[] {""};
	                }
	            } catch (Exception e) {
	                Log.e(LOG_TAG, "Error while reading from file "
	                        + file.getPath() + "." ,e);
	                ret = null;
	            } finally {
	                if (br != null) {
	                    try {
	                        br.close();
	                    }
	                    catch (IOException e) {
	                        Log.e(LOG_TAG, "Error while closing file.", e);
	                        ret = null;
	                    }
	                }
	            }
	        }
	        return ret;
	    }
	    public static int getOperationInfoForBlock(byte c1, byte c2, byte c3,
	            Operations op, boolean isSectorTrailer, boolean isKeyBReadable) {
	        // Is Sector Trailer?
	        if (isSectorTrailer) {
	            // Sector Trailer.
	            if (op != Operations.ReadKeyA && op != Operations.ReadKeyB
	                    && op != Operations.ReadAC
	                    && op != Operations.WriteKeyA
	                    && op != Operations.WriteKeyB
	                    && op != Operations.WriteAC) {
	                // Error. Sector Trailer but no Sector Trailer permissions.
	                return 4;
	            }
	            if          (c1 == 0 && c2 == 0 && c3 == 0) {
	                if (op == Operations.WriteKeyA
	                        || op == Operations.WriteKeyB
	                        || op == Operations.ReadKeyB
	                        || op == Operations.ReadAC) {
	                    return 1;
	                }
	                return 0;
	            } else if   (c1 == 0 && c2 == 1 && c3 == 0) {
	                if (op == Operations.ReadKeyB
	                        || op == Operations.ReadAC) {
	                    return 1;
	                }
	                return 0;
	            } else if   (c1 == 1 && c2 == 0 && c3 == 0) {
	                if (op == Operations.WriteKeyA
	                        || op == Operations.WriteKeyB) {
	                    return 2;
	                }
	                if (op == Operations.ReadAC) {
	                    return 3;
	                }
	                return 0;
	            } else if   (c1 == 1 && c2 == 1 && c3 == 0) {
	                if (op == Operations.ReadAC) {
	                    return 3;
	                }
	                return 0;
	            } else if   (c1 == 0 && c2 == 0 && c3 == 1) {
	                if (op == Operations.ReadKeyA) {
	                    return 0;
	                }
	                return 1;
	            } else if   (c1 == 0 && c2 == 1 && c3 == 1) {
	                if (op == Operations.ReadAC) {
	                    return 3;
	                }
	                if (op == Operations.ReadKeyA
	                        || op == Operations.ReadKeyB) {
	                    return 0;
	                }
	                return 2;
	            } else if   (c1 == 1 && c2 == 0 && c3 == 1) {
	                if (op == Operations.ReadAC) {
	                    return 3;
	                }
	                if (op == Operations.WriteAC) {
	                    return 2;
	                }
	                return 0;
	            } else if   (c1 == 1 && c2 == 1 && c3 == 1) {
	                if (op == Operations.ReadAC) {
	                    return 3;
	                }
	                return 0;
	            } else {
	                return -1;
	            }
	        } else {
	            // Data Block.
	            if (op != Operations.Read && op != Operations.Write
	                    && op != Operations.Increment
	                    && op != Operations.DecTransRest) {
	                // Error. Data block but no data block permissions.
	                return -1;
	            }
	            if          (c1 == 0 && c2 == 0 && c3 == 0) {
	                return (isKeyBReadable) ? 1 : 3;
	            } else if   (c1 == 0 && c2 == 1 && c3 == 0) {
	                if (op == Operations.Read) {
	                    return (isKeyBReadable) ? 1 : 3;
	                }
	                return 0;
	            } else if   (c1 == 1 && c2 == 0 && c3 == 0) {
	                if (op == Operations.Read) {
	                    return (isKeyBReadable) ? 1 : 3;
	                }
	                if (op == Operations.Write) {
	                    return 2;
	                }
	                return 0;
	            } else if   (c1 == 1 && c2 == 1 && c3 == 0) {
	                if (op == Operations.Read
	                        || op == Operations.DecTransRest) {
	                    return (isKeyBReadable) ? 1 : 3;
	                }
	                return 2;
	            } else if   (c1 == 0 && c2 == 0 && c3 == 1) {
	                if (op == Operations.Read
	                        || op == Operations.DecTransRest) {
	                    return (isKeyBReadable) ? 1 : 3;
	                }
	                return 0;
	            } else if   (c1 == 0 && c2 == 1 && c3 == 1) {
	                if (op == Operations.Read || op == Operations.Write) {
	                    return 2;
	                }
	                return 0;
	            } else if   (c1 == 1 && c2 == 0 && c3 == 1) {
	                if (op == Operations.Read) {
	                    return 2;
	                }
	                return 0;
	            } else if   (c1 == 1 && c2 == 1 && c3 == 1) {
	                return 0;
	            } else {
	                // Error.
	                return -1;
	            }
	        }
	    }


}
